package com.gitee.free.coding.admin.system.aspect;

import com.gitee.free.coding.admin.system.req.SystemLoginReq;
import com.gitee.free.coding.admin.system.service.ISystemLoginLogService;
import com.gitee.free.coding.admin.system.service.ISystemUserService;
import com.gitee.free.coding.admin.system.vo.SystemLoginLogVO;
import com.gitee.free.coding.admin.system.enums.LoginStatusEnum;
import com.gitee.free.coding.core.exceptions.FreeRuntimeException;
import com.gitee.free.coding.core.utils.DateUtils;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.validation.ConstraintViolationException;
import java.util.Arrays;

/**
 * 登录切面，可用于记录登录日志
 */
@Slf4j
@Order(value = 1000)
@Aspect
@Component
public class LoginAspect {

    @Autowired
    private ISystemLoginLogService systemLoginLogService;

    @Autowired
    private ISystemUserService systemUserService;

    /**
     * 切入到登录方法
     */
    @Pointcut("execution(* com.gitee.free.coding.admin.system.service.ISystemUserAuthService.login(..))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
        SystemLoginReq systemLoginReq = getSystemLoginReq(proceedingJoinPoint);
        SystemLoginLogVO systemLoginLogVO = new SystemLoginLogVO();
        initSystemLoginLogVO(systemLoginReq, systemLoginLogVO);
        try {
            log.info("用户[{}]登录中...", systemLoginReq.getUserName());
            Object result = proceedingJoinPoint.proceed();
            log.info("用户[{}]登录成功", systemLoginReq.getUserName());
            return result;
        } catch (FreeRuntimeException | MyBatisSystemException | ConstraintViolationException e) {
            systemLoginLogVO.setStatus(LoginStatusEnum.FAIL.getStatusCode());
            systemLoginLogVO.setRemark(LoginStatusEnum.FAIL.getDesc());
            // MyBatisSystemException: MybatisPlus防全局更新或删除异常; ConstraintViolationException: JSR 380 参数校验异常
            // 交给统一异常处理器处理
            throw e;
        } catch (Throwable e) {
            systemLoginLogVO.setStatus(LoginStatusEnum.FAIL.getStatusCode());
            systemLoginLogVO.setRemark(LoginStatusEnum.FAIL.getDesc());
            log.error(e.getMessage(), e);
            throw new FreeRuntimeException(-1, "登录拦截处理失败");
        } finally {
            // 新增一条登录日志
            systemLoginLogService.addSystemLoginLog(systemLoginLogVO);
        }
    }

    /**
     * 初始化登录日志信息SystemLoginLogVO
     *
     * @param systemLoginReq   登录请求参数
     * @param systemLoginLogVO 登录日志对象
     */
    private void initSystemLoginLogVO(SystemLoginReq systemLoginReq, SystemLoginLogVO systemLoginLogVO) {
        UserAgent userAgent = UserAgent.parseUserAgentString(systemLoginReq.getUserAgent());
        systemLoginLogVO.setUserName(systemLoginReq.getUserName());
        systemLoginLogVO.setIpAddress(systemLoginReq.getIpAddress());
        systemLoginLogVO.setDeviceType(userAgent.getOperatingSystem().getDeviceType().getName());
        systemLoginLogVO.setOptSystemType(userAgent.getOperatingSystem().getName());
        systemLoginLogVO.setBrowserType(userAgent.getBrowser().getName());
        systemLoginLogVO.setStatus(LoginStatusEnum.SUCCESS.getStatusCode());
        systemLoginLogVO.setRemark(LoginStatusEnum.SUCCESS.getDesc());
        systemLoginLogVO.setLoginDate(DateUtils.getCurrentDate());
        systemLoginLogVO.setLoginTime(DateUtils.getCurrentTime());
    }

    /**
     * 校验特定参数
     *
     * @param proceedingJoinPoint ProceedingJoinPoint
     * @return SystemLoginReq
     */
    private SystemLoginReq getSystemLoginReq(ProceedingJoinPoint proceedingJoinPoint) {
        return Arrays.stream(proceedingJoinPoint.getArgs())
                .filter(f -> f instanceof SystemLoginReq)
                .map(m -> (SystemLoginReq) m)
                .findFirst()
                .orElseThrow(() -> new FreeRuntimeException(-1, "该方法不存在特定参数[SystemLoginReq]或[SystemLoginReq == null]，请修改并使其执行前进入登录拦截"));
    }

}
